/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Engine2D.input;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;

/**
 *
 * @author Garys
 */
public class InputHandler {

    private int height;
    private int width;
    private boolean displayActive = true;
    private int lastMouseX;
    private int lastMouseY;
    private int doubleClickDelay = 250;
    private long doubleClickTimeout = 0;
    private int clickX;
    private int clickY;
    private int clickButton;
    private int pressedX = -1;
    private int pressedY = -1;
    private int mouseClickTolerance = 1;
    private int wheel;
    private float scaleX = 1;
    private float scaleY = 1;
    private float xoffset = 0;
    private float yoffset = 0;
    /**
     * 
     */
    protected char[] keys = new char[1024];
    /**
     * 
     */
    protected boolean[] pressed = new boolean[1024];
    /**
     * 
     */
    protected boolean[] mouseButtonPressed = new boolean[10];
    /**
     * 
     */
    protected HashSet allListeners = new HashSet();
    /**
     * 
     */
    protected ArrayList keyListeners = new ArrayList();
    /**
     * 
     */
    protected ArrayList keyListenersToAdd = new ArrayList();
    /**
     * 
     */
    protected ArrayList mouseListeners = new ArrayList();
    /**
     * 
     */
    protected ArrayList mouseListenersToAdd = new ArrayList();
    /**
     * 
     */
    protected boolean consumed = false;

    /**
     * 
     * @param height
     */
    public InputHandler(int height) {
        init(height);
    }

    /**
     * 
     */
    public InputHandler() {
        
    }

    /**
     * 
     * @param height
     */
    public void init(int height) {
        this.height = height;
        lastMouseX = getMouseX();
        lastMouseY = getMouseY();
    }

    /**
     * 
     * @param width
     * @param height
     */
    public void poll(int width, int height) {
        
        
        if (!Display.isActive()) {
            clearKeyPressedRecord();
            clearMousePressedRecord();
        }

        // add any key listeners requested since last time
        for (int i = 0; i < keyListenersToAdd.size(); i++) {
            addKeyListenerImpl((KeyListener) keyListenersToAdd.get(i));
        }
        keyListenersToAdd.clear();
        // add any mouse listeners requested since last time
        for (int i = 0; i < mouseListenersToAdd.size(); i++) {
            addMouseListenerImpl((MouseListener) mouseListenersToAdd.get(i));
        }
        mouseListenersToAdd.clear();


        if (doubleClickTimeout != 0) {
            if (System.currentTimeMillis() > doubleClickTimeout) {
                doubleClickTimeout = 0;
            }
        }

        this.height = height;

        // Notify all listeners input has started
        Iterator allStarts = allListeners.iterator();
        while (allStarts.hasNext()) {
            InputReciever listener = (InputReciever) allStarts.next();
            listener.inputStarted();
        }

        pollKeyboard();
        pollMouse();

    }

    private void pollMouse() {
        while (Mouse.next()) {
            //Check if buttons are pressed
            if (Mouse.getEventButton() >= 0) {
                if (Mouse.getEventButtonState()) {
                    consumed = false;
                    mouseButtonPressed[Mouse.getEventButton()] = true;

                    pressedX = (int) (xoffset + (Mouse.getEventX() * scaleX));
                    pressedY = (int) (yoffset + (((height - 1) - Mouse.getEventY()) * scaleY));
                    

                    //Notify Mouse listeners
                    for (int i = 0; i < mouseListeners.size(); i++) {
                        MouseListener listener = (MouseListener) mouseListeners.get(i);
                        if (listener.isAcceptingInput()) {
                            listener.mousePressed(Mouse.getEventButton(), pressedX, pressedY);
                            if (consumed) {
                                break;
                            }
                        }
                    }

                } else {

                    consumed = false;
                    mouseButtonPressed[Mouse.getEventButton()] = false;

                    int releasedX = (int) (xoffset + (Mouse.getEventX() * scaleX));
                    int releasedY = (int) (yoffset + (((height - 1) - Mouse.getEventY()) * scaleY));

                    if ((pressedX != -1)
                            && (pressedY != -1)
                            && (Math.abs(pressedX - releasedX) < mouseClickTolerance)
                            && (Math.abs(pressedY - releasedY) < mouseClickTolerance)) {
                        checkForDoubleClick(Mouse.getEventButton(), releasedX, releasedY);
                        pressedX = pressedY = -1;
                    }



                    //Notify Mouse listeners
                    for (int i = 0; i < mouseListeners.size(); i++) {
                        MouseListener listener = (MouseListener) mouseListeners.get(i);
                        if (listener.isAcceptingInput()) {
                            listener.mouseReleased(Mouse.getEventButton(), releasedX, releasedY);
                            if (consumed) {
                                break;
                            }
                        }
                    }
                }


            } else {
                if (Mouse.isGrabbed() && displayActive) {
                    if ((Mouse.getEventDX() != 0) || (Mouse.getEventDY() != 0)) {
                        consumed = false;
                        //Notify Mouse listeners
                        for (int i = 0; i < mouseListeners.size(); i++) {
                            MouseListener listener = (MouseListener) mouseListeners.get(i);
                            if (listener.isAcceptingInput()) {
                                if (anyMouseDown()) {
                                    listener.mouseDragged(0, 0, Mouse.getEventDX(), -Mouse.getEventDY());
                                } else {
                                    listener.mouseMoved(0, 0, Mouse.getEventDX(), -Mouse.getEventDY());
                                }
                                if (consumed) {
                                    break;
                                }
                            }
                        }

                    }

                }

                int wheelValue = Mouse.getEventDWheel();
                wheel += wheelValue;
                if (wheelValue != 0) {
                    consumed = false;
                    for (int i = 0; i < mouseListeners.size(); i++) {
                        MouseListener listener = (MouseListener) mouseListeners.get(i);
                        if (listener.isAcceptingInput()) {
                            listener.mouseWheelMoved(wheelValue);
                            if (consumed) {
                                break;
                            }
                        }
                    }
                }
            }
        }

        if (!displayActive || Mouse.isGrabbed()) {
            lastMouseX = getMouseX();
            lastMouseY = getMouseY();
        } else {
            if ((lastMouseX != getMouseX()) || (lastMouseY != getMouseY())) {
                consumed = false;
                for (int i = 0; i < mouseListeners.size(); i++) {
                    MouseListener listener = (MouseListener) mouseListeners.get(i);
                    if (listener.isAcceptingInput()) {
                        if (anyMouseDown()) {
                            listener.mouseDragged(lastMouseX, lastMouseY, getMouseX(), getMouseY());
                        } else {
                            listener.mouseMoved(lastMouseX, lastMouseY, getMouseX(), getMouseY());
                        }
                        if (consumed) {
                            break;
                        }
                    }
                }
                lastMouseX = getMouseX();
                lastMouseY = getMouseY();
            }
        }

    }

    private void pollKeyboard() {
        while (Keyboard.next()) {
            if (Keyboard.getEventKeyState()) {
                int eventKey = Keyboard.getEventKey();
                keys[eventKey] = Keyboard.getEventCharacter();
                pressed[eventKey] = true;
                consumed = false;


                //Notify Key listeners
                for (int i = 0; i < keyListeners.size(); i++) {
                    KeyListener listener = (KeyListener) keyListeners.get(i);
                    if (listener.isAcceptingInput()) {
                        listener.keyPressed(eventKey, Keyboard.getEventCharacter());
                        if (consumed) {
                            break;
                        }
                    }
                }

            } else {
                int eventKey = Keyboard.getEventKey();


                //Notify Key listeners
                for (int i = 0; i < keyListeners.size(); i++) {
                    KeyListener listener = (KeyListener) keyListeners.get(i);
                    if (listener.isAcceptingInput()) {
                        listener.keyPressed(eventKey, Keyboard.getEventCharacter());
                        if (consumed) {
                            break;
                        }
                    }
                }
            }
        }

    }

    /**
     * 
     * @return
     */
    public int getMouseX() {
        return (int) (xoffset + (Mouse.getX() * scaleX));
    }

    /**
     * 
     * @return
     */
    public int getMouseY() {
        return (int) (yoffset + (((height - 1) - Mouse.getEventY()) * scaleY));
    }

    private void addKeyListenerImpl(KeyListener keyListener) {
        if (keyListeners.contains(keyListener)) {
            return;
        }
        keyListeners.add(keyListener);
        allListeners.add(keyListener);
    }

    /**
     * 
     * @param keyListener
     */
    public void addKeyListener(KeyListener keyListener) {
        keyListenersToAdd.add(keyListener);
    }

    /**
     * 
     * @param keyCode
     * @return
     */
    public boolean isKeyPressed(int keyCode) {
        if (pressed[keyCode]) {
            pressed[keyCode] = false;
            return true;
        }

        return false;
    }

    /**
     * 
     * @param keyCode
     * @return
     */
    public boolean isKeyDown(int keyCode) {
        return Keyboard.isKeyDown(keyCode);
    }

    /**
     * 
     * @param mouseButton
     * @return
     */
    public boolean isMousePressed(int mouseButton) {
        if (mouseButtonPressed[mouseButton]) {
            mouseButtonPressed[mouseButton] = false;
            return true;
        }

        return false;
    }

    /**
     * 
     * @param mouseButton
     * @return
     */
    public boolean isMouseButtonDown(int mouseButton) {
        return Mouse.isButtonDown(mouseButton);
    }

    /**
     * 
     */
    public void consumeEvent() {
        consumed = true;
    }

    private void addMouseListenerImpl(MouseListener mouseListener) {
        if (mouseListeners.contains(mouseListener)) {
            return;
        }
        mouseListeners.add(mouseListener);
        allListeners.add(mouseListener);
    }

    /**
     * 
     * @param mouseListener
     */
    public void addMouseListener(MouseListener mouseListener) {
        mouseListenersToAdd.add(mouseListener);
    }

    /**
     * 
     */
    public void clearKeyPressedRecord() {
        Arrays.fill(pressed, false);
    }

    /**
     * 
     */
    public void clearMousePressedRecord() {
        Arrays.fill(mouseButtonPressed, false);
    }

    private void checkForDoubleClick(int eventButton, int pressedX, int pressedY) {
        if (doubleClickTimeout == 0) {
            clickX = pressedX;
            clickY = pressedY;
            clickButton = eventButton;
            doubleClickTimeout = System.currentTimeMillis() + doubleClickDelay;
            mouseClick(eventButton, pressedX, pressedY, 1);
        } else {
            if (clickButton == eventButton) {
                if ((System.currentTimeMillis() < doubleClickTimeout)) {
                    mouseClick(eventButton, pressedX, pressedY, 2);
                    doubleClickTimeout = 0;
                }
            }
        }
    }

    private boolean anyMouseDown() {
        for (int i = 0; i < 3; i++) {
            if (Mouse.isButtonDown(i)) {
                return true;
            }
        }

        return false;
    }

    private void mouseClick(int eventButton, int pressedX, int pressedY, int clickCount) {
        consumed = false;
        for (int i = 0; i < mouseListeners.size(); i++) {
            MouseListener listener = (MouseListener) mouseListeners.get(i);
            if (listener.isAcceptingInput()) {
                listener.mouseClicked(eventButton, pressedX, pressedY, clickCount);
                if (consumed) {
                    break;
                }
            }
        }

    }
}
